﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DistributedGraph
{
    /// <summary>
    /// Bildet einen Knoten ab.
    /// </summary>
    public class Node : IComparable
    {
        #region Konstanten
        private static int NEXTID = 1;
        private static object syncObject = new object();
        #endregion Konstanten

        #region Typen
        public delegate void SimpleMsgEvent(Node sender, Node receiver, Link link, IMessage message);
        public delegate void SimpleEvent(Node node);
        #endregion Typen

        #region Attribute
        private int id;
        private List<Link> links = new List<Link>();
        private bool failstops = false;
        // failbyz, faillink
        #endregion Attribute

        #region Eigenschaften
        /// <summary>
        /// Gibt die Id des Knotens an.
        /// </summary>
        public int Id
        {
            get { return id; }
        }

        /// <summary>
        /// Gibt eine Reihung aller zugehöriger Kanten zurück.
        /// </summary>
        public Link[] Links
        {
            get { return links.ToArray(); }
        }

        /// <summary>
        /// Ereignis das ausgelöst wird, wenn eine Nachricht vom Basisknoten ausgesendet wird.
        /// </summary>
        public event SimpleMsgEvent MsgSent;

        /// <summary>
        /// Ereignis das ausgelöst wird, wenn eine Nachricht vom Basisknoten empfangen wird.
        /// </summary>
        public event SimpleMsgEvent MsgReceived;

        /// <summary>
        /// Ereignis das ausgelöst wird, wenn der Knoten ausfällt.
        /// </summary>
        public event SimpleEvent Stopping;

        /// <summary>
        /// Gibt oder Setzt den Indikator ob der Knoten einem fiktivem Prozessfehler unterliegt.
        /// </summary>
        public bool FailStops
        {
            get { return failstops; }
            set
            {
                failstops = value;
                if (value && Stopping != null) Stopping(this);
            }
        }
        #endregion Eigenschaften

        #region Methoden
        /// <summary>
        /// Instanziiert und initialisiert die Klasse.
        /// </summary>
        public Node()
        {
            lock (syncObject)
            {
                this.id = NEXTID++;
            }
        }

        /// <summary>
        /// Setzt die Id zurück. (Nur anwenden wenn keine Instanzen mehr existieren!)
        /// </summary>
        public static void ResetID()
        {
            NEXTID = 1;
        }

        /// <summary>
        /// Zur Debugausgabe der bezeichnenden Zeichenkette.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Knoten #" + id.ToString();
        }

        /// <summary>
        /// Fügt eine Kante an den Knoten an.
        /// </summary>
        /// <param name="link">Die zuzufügende Kante.</param>
        internal void AddLink(Link link)
        {
            links.Add(link);
        }

        /// <summary>
        /// Gibt den Link zu einem spezifischen Knoten an, falls dieser existiert. Ansonsten wird NULL zurückgegeben.
        /// </summary>
        /// <param name="targetNode">Der Zielknoten.</param>
        /// <returns>Der Link oder Null wenn er nicht existiert.</returns>
        public Link GetLinkTo(Node targetNode)
        {
            int i = 0;
            while (i < links.Count &&
                ((links[i].LinkType != LinkType.Undirected || (links[i].SourceNode != targetNode && links[i].TargetNode != targetNode)) &&
                (links[i].LinkType != LinkType.Directed || links[i].TargetNode != targetNode))) i++;
            return i < links.Count ? links[i] : null;
        }

        /// <summary>
        /// Sendet eine Nachricht über eine Kante.
        /// </summary>
        /// <param name="link">Die Kante.</param>
        /// <param name="message">Die Nachricht.</param>
        public virtual void Send(Link link, IMessage message)
        {
            if (failstops) return;
            if (!links.Contains(link)) throw new Exception("Die angegebene Kante gehört nicht zu diesem Knoten.");
            if (MsgSent != null) MsgSent(this, (link.TargetNode == this ? link.SourceNode : link.TargetNode), link, message);
            link.Transport(this, message);
        }

        /// <summary>
        /// Broadcasted eine Nachricht an alle verbundenen Knoten.
        /// </summary>
        /// <param name="message">Die Nachricht.</param>
        public virtual void Broadcast(IMessage message)
        {
            foreach (Link l in links)
            {
                if (l.LinkType == LinkType.Undirected || l.SourceNode == this) Send(l, message);
            }
        }

        /// <summary>
        /// Empfängt eine Nachricht.
        /// </summary>
        /// <param name="link">Die Kante von der die Übertragung kommt.</param>
        /// <param name="message">Die übermitttelte Nachricht.</param>
        internal virtual void Receive(Link link, IMessage message)
        {
            if (!links.Contains(link)) throw new Exception("Die angegebene Kante gehört nicht zu diesem Knoten.");
            if (MsgReceived != null) MsgReceived((link.TargetNode == this ? link.SourceNode : link.TargetNode), this, link, message);
        }

        /// <summary>
        /// Erstellt einen N-N Graphen.
        /// </summary>
        /// <typeparam name="T">Der Knotentyp.</typeparam>
        /// <param name="nodeCount">Die Anzahl der Knoten.</param>
        /// <returns>Der Graph.</returns>
        public static List<T> CreateNtoNGraph<T>(int nodeCount) where T : Node, new()
        {
            List<T> nodes = new List<T>();
            Node.ResetID();
            Link.ResetID();
            for (int i = 0; i < nodeCount; i++) nodes.Add(new T());
            foreach (Node originNode in nodes) foreach (Node targetNode in nodes) if (originNode != targetNode && originNode.GetLinkTo(targetNode) == null) new Link(LinkType.Undirected, originNode, targetNode);
            return nodes;
        }

        /// <summary>
        /// Für den Vergleich via IComparable.
        /// </summary>
        /// <param name="other">Der andere Knoten.</param>
        /// <returns>0 = equal, -1 = lesser, 1 = greater</returns>
        public int CompareTo(object other) { return this.id - ((Node)other).Id; }
        #endregion Methoden
    }
}
